#include <sys/stat.h>
#include <fcntl.h>
#include "lc_six_to_four_op_Operator.h"
#include "UDP_Packet.hh"
#include "UDP6_Packet.hh"
#include "TCP_Packet.hh"
#include "TCP6_Packet.hh"
#include <iomanip>
#define BUF_SIZE 2048
#define PORT_BASE 9000

void
six_to_four_op::my_install(void * data)
{
}

void * 
six_to_four_op::my_get()
{
  return NULL;
}

void
six_to_four_op::my_purge(void * data)
{
}

void 
six_to_four_op::my_init()
{
  currPort = PORT_BASE;
  dstAddr = (0xa << 24) | (0x1 << 16) | (0x1 << 8) | 0x2;
  srcAddr = (0xa << 24) | (0x1 << 16) | (0x1 << 8) | 0x64;
}

pktTypes::Packet *
six_to_four_op::gendata_inRequest()
{
  pktTypes::Packet * pkt = new pktTypes::Packet(new char[BUF_SIZE], BUF_SIZE);
  pktTypes::Eth_Packet eth6(*pkt);
  pktTypes::IP6_Packet ip6(eth6);
//   pktTypes::UDP6_Packet udp6(ip6);
  pktTypes::TCP6_Packet tcp6(ip6);
  
  // transfer ip values
  
  ip6.setIPVersion();
  ip6.setPayloadLength(100);
  ip6.setNextHeader(IPPROTO_TCP);
  ip6.setHopLimit(255);
  
  // get source/dest addresses from flow state
  
  in6_addr src;
  uint32_t src_core = (0xa << 24) | (0x1 << 16) | (0x1 << 8) | 0xb;
  src.s6_addr32[0] = src_core;
  src.s6_addr32[1] = 0;
  src.s6_addr32[2] = 0;
  src.s6_addr32[3] = 0;
  in6_addr dst;
  uint32_t dst_core = (0xa << 24) | (0x1 << 16) | (0x1 << 8) | 0x3;
  dst.s6_addr32[0] = dst_core;
  dst.s6_addr32[1] = 0;
  dst.s6_addr32[2] = 0;
  dst.s6_addr32[3] = 0;

  ip6.setSourceAddress(src);
  ip6.setDestinationAddress(dst);
  
  // transfer tcp values
  tcp6.setSrcPort(htons(6000));
  tcp6.setDstPort(htons(5000));
  tcp6.setSeqNum(htonl(1000));
  tcp6.setAckSeqNum(htonl(1000));
  tcp6.setDataOff(0);
  tcp6.setFin(false);
  tcp6.setSyn(false);
  tcp6.setRst(false);
  tcp6.setPsh(false);
  tcp6.setAck(false);
  tcp6.setUrg(false);
  tcp6.setWindow(0);
  tcp6.setUrgPtr(0);
  tcp6.chksum();

//   // set upd values
//   udp6.setSrcPort(6000);
//   udp6.setDstPort(5000);
//   udp6.setUDPLength(100);
//   udp6.chksum();

  lagniappe::FlowID fid = pkt->getSixFiveTupleID();
  //std::cerr << "(" << pkt << ")ofid: " << fid << std::endl;
  tt::FiveTuple ft;
  ft.saddr = src_core;
  ft.daddr = dst_core;
  ft.sport = 6000;
  ft.dport = 5000;
  ft.proto = IPPROTO_UDP;
  FourMap_p fm(new FourMap(getNewMutex()));
  fm->setCore(ft);
  flowMapping[fid] = fm;

  return pkt;
}

void 
six_to_four_op::handleMyPort(pktTypes::Packet * data, unsigned long dataSize)
{
  assert(data != NULL);

  lagniappe::FlowID fid = getFlowID(data);
  FourMap_p fm;

  pktTypes::Eth_Packet eth6(*data);
  pktTypes::IP6_Packet ip6(eth6);
  pktTypes::TCP6_Packet tcp6(ip6);
  //pktTypes::UDP6_Packet udp6(ip6);
  
  pktTypes::Packet * pkt = new pktTypes::Packet(new char[BUF_SIZE], BUF_SIZE);
  memset((void *)pkt->getPayload(), '\0', BUF_SIZE);
  pktTypes::Eth_Packet eth(*pkt);
  pktTypes::IP_Packet ip(eth);
  pktTypes::TCP_Packet tcp(ip);
  //pktTypes::UDP_Packet udp(ip);

  if(inProf) {
    //std::cerr << "(" << data << ")fid: " << fid << std::endl;
  }

  //std::cerr << "fid: " << fid << std::endl;

  if(!flowMapping[fid]) {
    FourMap_p fmtemp(new FourMap(getNewMutex()));
    tt::FiveTuple tempFT;
    tempFT.sport = currPort++;
    //tempFT.sport = tcp6.getSrcPort();
    //tempFT.dport = udp6.getDstPort();
    tempFT.dport = tcp6.getDstPort();
    tempFT.saddr = srcAddr;
    tempFT.daddr = dstAddr;
    tempFT.proto = ip6.getNextHeader();
    fmtemp->setCore(tempFT);
    tt::SixFiveTuple sixFT;
    sixFT.saddr = ip6.getSourceAddress();
    sixFT.daddr = ip6.getDestinationAddress();
    //sixFT.sport = udp6.getSrcPort();
    //sixFT.dport = udp6.getDstPort();
    sixFT.sport = tcp6.getSrcPort();
    sixFT.dport = tcp6.getDstPort();
    sixFT.proto = ip6.getNextHeader();
    requestTypes::ConnInfo * newconn = new requestTypes::ConnInfo(tempFT, sixFT);
    new_connection_out(newconn, sizeof(requestTypes::ConnInfo));
    fm = flowMapping[fid] = fmtemp;
  }
  else {
    fm = flowMapping[fid];
  }

  tt::FiveTuple fiveTuple = fm->getCore();

  // transfer ip values

  ip.validate(true);
  ip.setTotLen(ip6.getPayloadLength() + pktTypes::IPv4_HSIZE);
  ip.setTtl(ip6.getHopLimit() - 1);
  ip.setProt(ip6.getNextHeader());
  ip.setFragOff(0);
  ip.setId(0);
  ip.setTos(0);
  
  // get source/dest addresses from flow state

  ip.setSrcAddr(fiveTuple.saddr);
  ip.setDstAddr(fiveTuple.daddr);
  ip.chksum();

  // transfer tcp values
  tcp.setDstPort(tcp6.getDstPort());
  tcp.setSeqNum(tcp6.getSeqNum());
  tcp.setAckSeqNum(tcp6.getAckSeqNum());
  tcp.setDataOff(tcp6.getDataOff());
  tcp.setFin(tcp6.getFin());
  tcp.setSyn(tcp6.getSyn());
  tcp.setRst(tcp6.getRst());
  tcp.setPsh(tcp6.getPsh());
  tcp.setAck(tcp6.getAck());
  tcp.setUrg(tcp6.getUrg());
  tcp.setWindow(tcp6.getWindow());
  tcp.setUrgPtr(tcp6.getUrgPtr());
  tcp.setRes2(tcp6.getRes2());

  // get source from flow state

  tcp.setSrcPort(fiveTuple.sport);

//   // transfer udp values

//   udp.setDstPort(udp6.getDstPort());
//   udp.setUDPLength(udp6.getUDPLength());

//   // get source from flow state

//   udp.setSrcPort(fiveTuple.sport);

  // copy payload

  //memcpy(udp.getPayload(), udp6.getPayload(), ip6.getPayloadLength() - pktTypes::UDP_HSIZE);
  memcpy(tcp.getPayload(), tcp6.getPayload(), ip6.getPayloadLength() - pktTypes::UDP_HSIZE);

  // calculate checksum

//   tcp.chksum();
  tcp.chksum();

  pkt->setLength(pktTypes::ETH_HSIZE + ip.getTotLen());


  pktTypes::ethAddr ea;

  //00:15:17:62:9D:7E

  ea.data[5] = 0x7E;
  ea.data[4] = 0x9D;
  ea.data[3] = 0x62;
  ea.data[2] = 0x17;
  ea.data[1] = 0x15;
  ea.data[0] = 0x00;

  eth.setSrcAddress(ea);

  //00:04:23:BD:59:76

//   ea.data[5] = 0x76;
//   ea.data[4] = 0x59;
//   ea.data[3] = 0xBD;
//   ea.data[2] = 0x23;
//   ea.data[1] = 0x04;
//   ea.data[0] = 0x00;

  //00:04:23:BD:5B:F1

  ea.data[5] = 0xF1;
  ea.data[4] = 0x5B;
  ea.data[3] = 0xBD;
  ea.data[2] = 0x23;
  ea.data[1] = 0x04;
  ea.data[0] = 0x00;

  eth.setDstAddress(ea);

  // set IP Protocol appropriately

  eth.setEthProt(ETH_P_IP);

  // Artifically make operator more expensive

//   uint32_t highRes = 0;
//   srand(fm->getRand());
//   for(int i=0; i < 10; ++i) {
//     uint32_t res = rand();
//     if(res > highRes) highRes = res;
//   }
//   fm->setRand(highRes);

  data_out(pkt, sizeof(pktTypes::Packet));

  if(!inProf) {
    delete[] data->getPayload();
    delete data;
  }
  else {
    delete[] pkt->getPayload();
    delete pkt;
  }

  return;
}


